package com.superbytecode.cloud.common.utils;

import cn.hutool.core.collection.CollectionUtil;
import com.superbytecode.cloud.common.annotation.field.FieldDict;
import com.superbytecode.cloud.common.annotation.field.FieldExplain;
import com.superbytecode.cloud.common.entities.diff.Difference;
import com.superbytecode.cloud.common.entities.diff.FieldProperty;
import lombok.experimental.UtilityClass;
import org.reflections.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * All rights Reserved, Designed By www.super-master.com
 *
 * @ProjectName: sanskrit
 * @Package: com.superbytecode.cloud.core.utils
 * @ClassName: CompareUtil
 * @Description: [比较工具]
 * <p> </p>
 * @Author: [Lance Ting]
 * @Date: 2024/8/16 16:46
 * @Version: V1.0
 * @Copyright: 2024 www.super-master.com Inc. All rights reserved.
 * TODO: 注意,本文件Lance Ting所作,如果转载或使用请标明具体出处!
 **/
@UtilityClass
public class CompareUtil {
    private static final Map<String, Set<Field>> beanFieldCache = new HashMap<>();
    // 排除对比字段值：如creator/createTime/modifier/modifyTime
    private static final Set<String> beanExcludeFiled = new HashSet<>(5);

    static {
        beanExcludeFiled.add("creator");
        beanExcludeFiled.add("createTime");
        beanExcludeFiled.add("modifier");
        beanExcludeFiled.add("modifyTime");
    }

    // -----------------------------------------------------------------------------------------------------------------

    /**
     * 比较基本Bean对象指定列是否变化
     *
     * @param targetObj         目标对象（新增）
     * @param sourceObj         原始对象（旧值）
     * @param settingFieldsName 指定列
     * @return true-变化，false-无变化
     */
    public boolean verifyBeanModify(Object targetObj, Object sourceObj, String... settingFieldsName) throws ReflectiveOperationException {
        return CollectionUtil.isNotEmpty(compareBeanAttributesDiffs(targetObj, sourceObj, true, true));
    }

    /**
     * 比较基本Bean对象指定列的差异
     *
     * @param targetObj         目标对象（新增）
     * @param sourceObj         原始对象（旧值）
     * @param settingFieldsName 指定列
     * @return 异类别列表
     * @throws IllegalAccessException    异常
     * @throws InvocationTargetException 异常
     * @throws NoSuchMethodException     异常
     */
    public List<Difference> compareObj(Object targetObj, Object sourceObj, String[] settingFieldsName) throws ReflectiveOperationException {
        AssertUtil.requireNonNull(targetObj, "target obj is null");
        // 初始化beanFieldCache
        loadFieldCachePreprocess(targetObj.getClass(), settingFieldsName);
        return compareBeanAttributesDiffs(targetObj, sourceObj, false, true);
    }

    // -----------------------------------------------------------------------------------------------------------------

    /**
     * 基本类型集合对比
     *
     * @param collTargets 目标集合（新增）
     * @param collSources 原始集合（新增）
     * @param <T>         约束类型
     * @return 差异结果
     */
    public <T> List<Difference> compareCollection(Collection<T> collTargets, Collection<T> collSources) {
        List<Difference> differences = new ArrayList<>();
        AssertUtil.requireNonNull(collTargets, "targets is null");
        AssertUtil.requireNonNull(collSources, "sources is null");
        Set<T> collections = new HashSet<>(collTargets.size() + collSources.size());
        Set<T> targets = new HashSet<>(collTargets);
        Set<T> sources = new HashSet<>(collSources);
        // 取原始数据和目标数据交集
        collections.addAll(sources);
        collections.retainAll(targets);
        if (VerifyUtil.compareEquals(collections.size(), targets.size(), sources.size())) {
            return differences; //没有变更
        }
        // 原始数据差集：删除元素
        collSources.removeAll(collections);
        if (CollectionUtil.isNotEmpty(collSources)) {
            differences.add(Difference.newInstance("del", FieldProperty.newInstance(collections, null)));
        }
        // 目标数据差集：新增元素
        collTargets.removeAll(collections);
        if (CollectionUtil.isNotEmpty(collTargets)) {
            differences.add(Difference.newInstance("add", FieldProperty.newInstance(collTargets, null)));
        }
        return differences;
    }

    // -----------------------------------------------------------------------------------------------------------------

    /**
     * 比较bean对象属性值变化
     *
     * @param targetObj 目标对象（新增）
     * @param sourceObj 原始对象（旧值）
     * @param checked   是否仅仅校验不同
     * @param selective 是否跳过目标属性为空的字段
     * @return 异类别列表
     * @throws IllegalAccessException    异常
     * @throws InvocationTargetException 异常
     * @throws NoSuchMethodException     异常
     */
    private List<Difference> compareBeanAttributesDiffs(Object targetObj, Object sourceObj, boolean checked, boolean selective) throws ReflectiveOperationException {
        List<Difference> differences = new ArrayList<>();
        // 对比较对象不能为空！
        boolean createStatus = Objects.isNull(sourceObj); // 新增逻辑
        AssertUtil.requireNonEmpty(targetObj, "target must not be null");
        // 对比较对象类型必须相同
        Class<?> sourceObjClass = sourceObj.getClass();
        Class<?> targetObjClass = targetObj.getClass();
        AssertUtil.requirePositive(Objects.equals(sourceObjClass, targetObjClass), "source and target are not the same class");
        if (sourceObj == targetObj) { // 同一个实例对象，必然无差异
            return differences;
        }
        for (Field field : loadBeanCompareFields(targetObjClass)) { //
            field.setAccessible(true);
            Object targetValue = field.get(targetObj);
            Object sourceValue = createStatus ? null : field.get(sourceObj);
            // 属性列名以及类型
            String fieldName = field.getName();
            String fieldTypeName = field.getType().getTypeName();
            // 忽略比较内容逻辑：
            if (beanExcludeFiled.contains(fieldName)// ①是忽略字段
                    || VerifyUtil.compareEquals(targetValue, sourceValue)//② 值相等（包含同为null的场景）
                    || (selective && Objects.isNull(targetValue))// ③、目标值为空&&selective
            ) {
                continue;
            }
            // 字段中文备注注解类
            FieldExplain fieldsExplain = field.getAnnotation(FieldExplain.class);
            String fieldsRemark = Objects.nonNull(fieldsExplain) ? fieldsExplain.value() : fieldName;
            // 字段value枚举类: 字段value是枚举code值，则取枚举name，否则直接取值
            FieldDict fieldsMapping = field.getAnnotation(FieldDict.class);
            FieldProperty property = Objects.nonNull(fieldsMapping)//构建差异性属性值
                    ? compareDictValue(fieldsMapping, targetValue, sourceValue)//
                    : FieldProperty.newInstance(targetValue, sourceValue);
            Difference difference = Difference.newInstance(fieldName, fieldTypeName, fieldsRemark, property);
            // 如果仅仅校验是否不同，则提前返回
            if (checked && CollectionUtil.isNotEmpty(differences)) {
                return differences;
            }
            differences.add(difference);
        }
        return differences;
    }

    /**
     * 记载字典（枚举）值
     * 枚举详见{@link com.superbytecode.cloud.common.enums.BaseEnum}对应的子类
     *
     * @param mapping     字段枚举直接类
     * @param targetValue 目标值（枚举code）
     * @param sourceValue 原始值（枚举code）
     * @return 枚举name
     * @throws NoSuchMethodException     异常1
     * @throws InvocationTargetException 异常2
     * @throws IllegalAccessException    异常3
     */
    private FieldProperty compareDictValue(FieldDict mapping, Object targetValue, Object sourceValue) throws ReflectiveOperationException {
        Class<?> enumType = mapping.enumType();
        Class<?> codeType = mapping.codeType();
        // 执行注解getEnumNameByCode方法
        final Method enumService = enumType.getDeclaredMethod("getEnumNameByCode", codeType);
        Object targetName = enumService.invoke(enumType, targetValue);
        Object sourceName = enumService.invoke(enumType, sourceValue);
        return FieldProperty.newInstance(targetName, sourceName);
    }

    /**
     * 加载类目标类所有public字段
     *
     * @param beanType bean类型
     * @return 所有字段
     */
    @SuppressWarnings("unchecked")
    private Set<Field> loadBeanCompareFields(Class<?> beanType) {
        String beanClassName = beanType.getName();
        Set<Field> fields = beanFieldCache.get(beanClassName);
        if (CollectionUtil.isEmpty(fields)) {
            synchronized (beanFieldCache) {
                if (CollectionUtil.isEmpty(fields)) {
                    beanFieldCache.put(beanClassName, ReflectionUtils.getAllFields(beanType));
                }
            }
        }
        fields = beanFieldCache.get(beanClassName);
        AssertUtil.requireNonNull(fields, "bean fields cache is null");
        return fields;
    }

    @SuppressWarnings("unchecked")
    private void loadFieldCachePreprocess(Class<?> beanType, String[] settingFieldsName) {
        AssertUtil.requireNonNull(beanType, "bean type is null");
        String beanClassName = beanType.getName();
        Set<Field> setterFields = ReflectionUtils.getAllFields(beanType);
        if (null != settingFieldsName) {
            setterFields = setterFields.stream().filter(s -> Arrays.asList(settingFieldsName).contains(s.getName())).collect(Collectors.toSet());
        }
        beanFieldCache.put(beanClassName, setterFields);
    }
}